package com.zdp.leetcodeMiddle;

import java.util.ArrayList;
import java.util.List;
/*
* 题目描述：
* 给定一个数字字符串 S，比如 S = "123456579"，我们可以将它分成斐波那契式的序列 [123, 456, 579]。
形式上，斐波那契式序列是一个非负整数列表 F，且满足：
0 <= F[i] <= 2^31 - 1，（也就是说，每个整数都符合 32 位有符号整数类型）；
F.length >= 3；
对于所有的0 <= i < F.length - 2，都有 F[i] + F[i+1] = F[i+2] 成立。
另外，请注意，将字符串拆分成小块时，每个块的数字一定不要以零开头，除非这个块是数字 0 本身。
返回从 S 拆分出来的任意一组斐波那契式的序列块，如果不能拆分则返回 []。
示例 1：
输入："123456579"
输出：[123,456,579]
示例 2：
输入: "11235813"
输出: [1,1,2,3,5,8,13]
示例 3：
输入: "112358130"
输出: []
解释: 这项任务无法完成。
示例 4：
输入："0123"
输出：[]
解释：每个块的数字不能以零开头，因此 "01"，"2"，"3" 不是有效答案。
示例 5：
输入: "1101111"
输出: [110, 1, 111]
解释: 输出 [11,0,11,11] 也同样被接受。 
提示：
1 <= S.length <= 200
字符串 S 中只含有数字。
来源：力扣（LeetCode）
链接：https://leetcode-cn.com/problems/split-array-into-fibonacci-sequence
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。
* */
public class 将数组拆分成斐波那契数列 {

    public static void main(String[] args) {
        将数组拆分成斐波那契数列 demo = new 将数组拆分成斐波那契数列();
        System.out.println(demo.splitIntoFibonacci("0000"));
    }
    /*
    * 回溯，探索数组前两个数的所有可能性，并根据题目要求进行剪枝
    * 首先要明确成功的条件--->递归的退出条件
    * 其次，因为斐波那契数组的规律，我们只需要回溯牵前两个数的所有可能性即可，
    * 后面的数根据前面的数来进行匹配即可
    * */
    public List<Integer> splitIntoFibonacci(String S) {
        List<Integer> list = new ArrayList<>();
        f(list,S,0);
        return list;
    }
    //该函数一次拆分一个子串
    //其实只需要遍历数组的前两个数的所有可能性即可（探索）
    public boolean f(List<Integer> list,String S,int index){
        //找一个终止的条件，如果字符串被拆分完毕，且列表长度大于等于3，
        //则说明可以拆分成斐波那契数列
        if(S.equals("")){
            return list.size()>=3;
        }

        //如果还未匹配完
        //则看看是要直接匹配，还是取数
        if(list.size()>=2){ //因为数组中已经有至少两个数了，所以接下来的数都已经被定死了
            //取数,计算
            int num = list.get(index-1)+list.get(index-2);
            //约定数组中的数大小
            if(num>Integer.MAX_VALUE)
                return false;
            String snum = String.valueOf(num);
            //看一下是否匹配，如果没有匹配就回溯，如果匹配就改变一下链表
            if(S.startsWith(snum)){
                list.add(num);
                //当前匹配成功，匹配下一个
                 if(f(list,S.substring(snum.length()),index+1)){
                     return true;
                 }
                 list.remove(index); //当回溯回来，记得把之前保存的数删除
            }
            //没有匹配成功就回溯
            return false;
        }else{
            //数组中的数少于两个，遍历这两个数的所有可能性
            for(int i=1;i<=S.length();i++){
                String snum = S.substring(0,i);
                //避免取到以0开头的数，不被允许 01 02 这种不被允许，但可以是0
                if(snum.startsWith("0")&&snum.length()>1) //剪枝
                    return false;
                //还要避免数过大.... 0~ 2^31-1,上面的匹配也要避免数过大
                //取的数要避免数过大，匹配的数也要避免数过大
                long c = Long.valueOf(snum);
                if(c>Integer.MAX_VALUE) //剪枝 ，就是剪掉不必要的尝试
                    break;
                list.add(Integer.valueOf(snum));
                if(f(list,S.substring(i),index+1)){
                    return true;
                }
                //不匹配就回溯
                list.remove(index);
            }
            return false;
        }

    }
}
